﻿using System;
using System.Html;
using Quicklight.Web.Commands;
using jQueryApi;

namespace Quicklight.Web.Html {

   public static class Extensions {

      /// <summary>
      /// Extension method that returns the jQueryObject for the specified element.
      /// </summary>
      /// <param name="element">Element to retrieve the jQueryObject for</param>
      /// <returns>jQueryObject that matches the supplied Element</returns>
      public static jQueryObject GetjQueryObject(this Element element) {
         string query = "#" + element.ID.Replace("(", @"\(").Replace(":", @"\:").Replace("|", @"\|").Replace(@"\", @"\\").Replace(".", @"\.").Replace(")", @"\)");
         return jQuery.Select(query);
      }

      /// <summary>
      /// Extension method that binds the command to the event, named eventName, on the window instance
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="window">HTML window instance obtained by using System.Html.Window.Instance</param>
      /// <param name="eventName">Name of the event to bind the command to</param>
      /// <param name="command">Command that will be executed if event fires</param>
      public static void Bind(this WindowInstance window, string eventName, ICommand command) {
         jQueryObject jQueryObject = jQuery.Window;
         jQueryObject.On(eventName, (e) => command.Execute());
      }

      /// <summary>
      /// Extension method that binds the handler to the event, named eventName, on the window instance
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="window">HTML window instance obtained by using System.Html.Window.Instance</param>
      /// <param name="eventName">Name of the event to bind the handler to</param>
      /// <param name="handler">Delegate that will be invoked if event fires</param>
      public static void Bind(this WindowInstance window, string eventName, ElementEventHandler handler) {
         jQueryObject jQueryObject = jQuery.Window;
         jQueryObject.On(eventName, (e) => handler());
      }

      /// <summary>
      /// Extension method that binds the listener to the event, named eventName, on the window instance
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="window">HTML window instance obtained by using System.Html.Window.Instance</param>
      /// <param name="eventName">Name of the event to bind the listener to</param>
      /// <param name="listener">Delegate that will be invoked if event fires</param>
      public static void Bind(this WindowInstance window, string eventName, ElementEventListener listener) {
         jQueryObject jQueryObject = jQuery.Window;
         jQueryObject.On(eventName, (e) => listener(null));
      }

      /// <summary>
      /// Extension method that binds the command to the event, named eventName, on the document instance
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="document">HTML document instance obtained by using System.Html.Document.Instance</param>
      /// <param name="eventName">Name of the event to bind the command to</param>
      /// <param name="command">Command that will be executed if event fires</param>
      public static void Bind(this DocumentInstance document, string eventName, ICommand command) {
         jQueryObject jQueryObject = jQuery.Document;
         jQueryObject.On(eventName, (e) => command.Execute());
      }

      /// <summary>
      /// Extension method that binds the handler to the event, named eventName, on the document instance
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="document">HTML document instance obtained by using System.Html.Document.Instance</param>
      /// <param name="eventName">Name of the event to bind the handler to</param>
      /// <param name="handler">Delegate that will be invoked if event fires</param>
      public static void Bind(this DocumentInstance document, string eventName, ElementEventHandler handler) {
         jQueryObject jQueryObject = jQuery.Document;
         jQueryObject.On(eventName, (e) => handler());
      }

      /// <summary>
      /// Extension method that binds the listener to the event, named eventName, on the document instance
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="document">HTML document instance obtained by using System.Html.Document.Instance</param>
      /// <param name="eventName">Name of the event to bind the listener to</param>
      /// <param name="listener">Delegate that will be invoked if event fires</param>
      public static void Bind(this DocumentInstance document, string eventName, ElementEventListener listener) {
         jQueryObject jQueryObject = jQuery.Document;
         jQueryObject.On(eventName, (e) => listener(null));
      }

      /// <summary>
      /// Extension method that binds the command to the event, named eventName, on the element
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="element">Element that will be used in binding</param>
      /// <param name="eventName">Name of the event to bind the command to</param>
      /// <param name="command">Command that will be executed if event fires</param>
      public static void Bind(this Element element, string eventName, ICommand command) {
         jQueryObject jQueryObject = jQuery.FromElement(element);

         jQueryObject.On(eventName, (e) => command.Execute());
         command.CanExecuteChanged += (s, e) => {
            if (command.CanExecute)
               jQueryObject.RemoveAttr("disabled");
            else
               jQueryObject.Attribute("disabled", "");
         };
      }

      /// <summary>
      /// Extension method that binds the handler to the event, named eventName, on the element
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="element">Element that will be used in binding</param>
      /// <param name="eventName">Name of the event to bind the handler to</param>
      /// <param name="handler">Delegate that will be invoked if event fires</param>
      public static void Bind(this Element element, string eventName, ElementEventHandler handler) {
         jQueryObject jQueryObject = jQuery.FromElement(element);
         jQueryObject.On(eventName, (e) => handler());
      }

      /// <summary>
      /// Extension method that binds the listener to the event, named eventName, on the element
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="element">Element that will be used in binding</param>
      /// <param name="eventName">Name of the event to bind the listener to</param>
      /// <param name="listener">Delegate that will be invoked if event fires</param>
      public static void Bind(this Element element, string eventName, ElementEventListener listener) {
         jQueryObject jQueryObject = jQuery.FromElement(element);
         jQueryObject.On(eventName, (e) => listener(null));
      }

      /// <summary>
      /// Extension method that binds the HandleEvent method on the handler to the event, named eventName, on the element
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="element">Element that will be used in binding</param>
      /// <param name="eventName">Name of the event to bind the handler to</param>
      /// <param name="handler">Interface that implements a handler named HandleEvent</param>
      public static void Bind(this Element element, string eventName, IElementEventHandler handler) {
         jQueryObject jQueryObject = jQuery.FromElement(element);
         jQueryObject.On(eventName, (e) => handler.HandleEvent(null));
      }

      /// <summary>
      /// Extension method that binds the content of the list to the attribute. 
      /// If the content of the list changes the attribute will be updated with the result of the ToString method of the list
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <typeparam name="T">Type of items stored in the ObservableList list</typeparam>
      /// <param name="targetElement">Element that will be used in binding</param>
      /// <param name="attribute">Attribute that will be updated with the content of the ObservableList</param>
      /// <param name="list">Observable list to use to bind to attribute</param>
      public static void Bind<T>(this Element targetElement, string attribute, ObservableList<T> list) {
         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         list.ListChanged += (s, e) => jQueryTarget.Attribute(attribute, list.ToString());
      }

      /// <summary>
      /// Extension method that binds the ObservableProperty to the attribute
      /// The update direction can be specified as well as the trigger for the update from the UI
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <typeparam name="T">Type that the ObservableProperty wraps</typeparam>
      /// <param name="targetElement">Element whose attribute must be bound to the ObservableProperty</param>
      /// <param name="attribute">attribute that must be bound to the ObservableProperty</param>
      /// <param name="property">ObservableProperty that must be bound to the attribute</param>
      /// <param name="triggerElementId">Optional. If the element that has the trigger event is an element other than the element of the attribute, specify its id here</param>
      /// <param name="eventName">Optional. Default = "change". Name of the event that will trigger an update from the UI to the model</param>
      /// <param name="updateModel">Optional. Default = true. Boolean indicating if the trigger event occurs whether the ObservableProperty must be updated with the value of the attribute</param>
      /// <param name="updateUI">Optional. Default = true. Boolean indicating if changes in the ObservableProperty must propagate to the attribute</param>
      public static void Bind<T>(this Element targetElement, string attribute, ObservableProperty<T> property, string triggerElementId = "", string eventName = "change", bool updateModel = true, bool updateUI = true) {
         Element triggerElement = null;
         if (String.IsNullOrEmpty(triggerElementId))
            triggerElement = targetElement;
         else
            triggerElement = Document.Instance.GetElementById(triggerElementId);

         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         jQueryObject jQueryTrigger = jQuery.FromElement(triggerElement);

         if (updateModel)
            jQueryTrigger.On(eventName, (e) => property.SetValue(jQueryTarget.GetAttribute(attribute)));

         if (updateUI)
            property.ValueChanged += (s, e) => jQueryTarget.Attribute(attribute, property.Value.ToString());
      }

      /// <summary>
      /// Extension method that binds the CalculatedProperty to the attribute
      /// Any changes in the value of the CalculatedProperty will be propagated to the attribute
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <typeparam name="T">Type that the CalculatedProperty wraps</typeparam>
      /// <param name="targetElement">Element whose attribute must be bound to the CalculatedProperty</param>
      /// <param name="attribute">attribute that must be bound to the CalculatedProperty</param>
      /// <param name="property">CalculatedProperty that must be bound to the attribute</param>
      public static void Bind<T>(this Element targetElement, string attribute, CalculatedProperty<T> property) {
         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         property.ValueChanged += (s, e) => jQueryTarget.Attribute(attribute, property.Value.ToString());
      }

      /// <summary>
      /// Extension method that binds the Validation instance to the attribute
      /// Any changes in the value of the Validation Message property will be propagated to the attribute
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="targetElement">Element whose attribute must be bound to the Validation Message property</param>
      /// <param name="attribute">attribute that must be bound to the Validation Message property</param>
      /// <param name="validation">Validation that must be bound to the attribute</param>
      public static void Bind(this Element targetElement, string attribute, Validation validation) {
         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         validation.MessageChanged += (s, e) => jQueryTarget.Attribute(attribute, validation.Message);
      }

      /// <summary>
      /// Extension method that binds the content of the list to the inner content of the element
      /// If the content of the list changes the content of the element will be updated with the result of the ToString method of the list
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <typeparam name="T">Type of items stored in the ObservableList list</typeparam>
      /// <param name="targetElement">Element that will be used in binding</param>
      /// <param name="list">Observable list to use to bind to content of the element</param>
      public static void Bind<T>(this Element targetElement, ObservableList<T> list) {
         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         list.ListChanged += (s, e) => jQueryTarget.Text(list.ToString());
      }

      /// <summary>
      /// Extension method that binds the ObservableProperty to the inner content of the element
      /// If the value of the ObservableProperty changes, the inner content of the element will be updated
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <typeparam name="T">Type that the ObservableProperty wraps</typeparam>
      /// <param name="targetElement">Element whose inner content must be bound to the ObservableProperty</param>
      /// <param name="property">ObservableProperty that must be bound to the content of the element</param>
      public static void Bind<T>(this Element targetElement, ObservableProperty<T> property) {
         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         property.ValueChanged += (s, e) => jQueryTarget.Text(property.Value.ToString());
      }

      /// <summary>
      /// Extension method that binds the CalculatedProperty to the inner content of the element
      /// Any changes in the value of the CalculatedProperty will be propagated to the inner content of the element
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <typeparam name="T">Type that the CalculatedProperty wraps</typeparam>
      /// <param name="targetElement">Element whose content must be bound to the CalculatedProperty</param>
      /// <param name="property">CalculatedProperty that must be bound to the content of the element</param>
      public static void Bind<T>(this Element targetElement, CalculatedProperty<T> property) {
         jQueryObject jQueryTarget = jQuery.FromElement(targetElement);
         property.ValueChanged += (s, e) => jQueryTarget.Text(property.Value.ToString());
      }

      /// <summary>
      /// Extension method that binds the Validation instance to the inner content of the element
      /// Any changes in the value of the Validation Message property will be propagated to the content of the element
      /// Used by programmatic and declarative binding methods
      /// </summary>
      /// <param name="element">Element whose content must be bound to the Validation Message property</param>
      /// <param name="validation">Validation that must be bound to the content of the element</param>
      public static void Bind(this Element element, Validation validation) {
         jQueryObject jQueryObject = jQuery.FromElement(element);
         validation.MessageChanged += (s, e) => {
            jQueryObject.Text(validation.Message);
            jQueryObject.Toggle(validation.Failed);
         };
      }


   }
}
